package txpool

import (
	"fmt"
	"osiris/config"
	"osiris/core/state"
	"osiris/dto"
	"osiris/logger"
	"sync"
)

// BaseTxPool 普通的交易池
type BaseTxPool struct {

	// queued 还不能立马在本地执行的交易（比如nonce值过高）（每一个addr中的交易按nonce从大到小排列）
	queued map[string]*TxDescendArray

	// pending nonce值完全合法的交易，等待被打包或者上链确认（每一个addr中的交易按nonce从小到大排列）
	pending map[string][]*dto.TxData

	// confirmSubChs 订阅交易确认的通道，和pending共用一个锁
	confirmSubChs []chan dto.TxData

	// queuedMutex
	queuedMutex *sync.RWMutex

	// pendingMutex
	pendingMutex *sync.RWMutex

	// queuedLimitSize 交易池queued的限制大小，交易数量达到此值后会定期执行queued的清理工作
	queuedLimitSize int

	// pendingLimitSize 交易池pending的限制大小，交易数量达到此值后会定期执行pending的清理工作
	pendingLimitSize int
}

func (pool *BaseTxPool) Init(queuedSize int, pendingSize int) {
	//TODO confirm: make的第二个参数
	pool.queued = make(map[string]*TxDescendArray, 0)
	pool.pending = make(map[string][]*dto.TxData, 0)
	pool.queuedMutex = new(sync.RWMutex)
	pool.pendingMutex = new(sync.RWMutex)
	pool.queuedLimitSize = queuedSize
	pool.pendingLimitSize = pendingSize
	pool.confirmSubChs = make([]chan dto.TxData, 0)
}

func (pool *BaseTxPool) Close() {
	//no need to do anything
}

func (pool *BaseTxPool) inQueued(txData *dto.TxData) {
	//queued满了会开启清理流程
	pool.queuedMutex.RLock()
	if len(pool.queued) >= pool.queuedLimitSize {
		pool.queuedMutex.RUnlock()
		pool.gc()
		logger.Warn(map[string]interface{}{"[txpool] [in Queued]": "reach limited size of queued, start clearing procedure"})

		//TODO clear
	}

	pool.queuedMutex.RUnlock()
	pool.queuedMutex.Lock()

	//在queued中的fromAddr对应的降序数组中插入交易
	if txDescendArray, exist := pool.queued[txData.FromAddr]; exist {
		txDescendArray.InsertTx(txData)
	} else {
		newArray := &TxDescendArray{}
		newArray.InsertTx(txData)
		pool.queued[txData.FromAddr] = newArray
	}

	pool.queuedMutex.Unlock()
	logger.Info(map[string]interface{}{fmt.Sprintf("[txpool] [in Queued] Tx [hash: %s] ", txData.TxHash): "succeed"})
}

// inPending 将交易后的状态写入状态树，若成功将交易加入pending
//
//	@receiver pool
//	@param txData
func (pool *BaseTxPool) inPending(txData *dto.TxData) {
	pool.pendingMutex.RLock()
	if len(pool.pending) >= pool.pendingLimitSize {
		pool.pendingMutex.RUnlock()
		pool.gc()
		logger.Warn(map[string]interface{}{"[txpool] [in Pending] reach limited size of pending, start clearing procedure": fmt.Sprintf("current size: %d limited size: %d", len(pool.pending), pool.pendingLimitSize)})
	} else {
		pool.pendingMutex.RUnlock()
	}

	pool.pendingMutex.Lock()
	if txs, exist := pool.pending[txData.FromAddr]; exist {
		txs = append(txs, txData)
		pool.pending[txData.FromAddr] = txs
	} else {
		pool.pending[txData.FromAddr] = []*dto.TxData{txData}
	}
	pool.pendingMutex.Unlock()
	logger.Info(map[string]interface{}{fmt.Sprintf("[txpool] [in Pending] Tx [hash: %s] ", txData.TxHash): "succeed"})

	//检查加入queued后能不能凑成起始牌为当前账户对应nonce的顺子
	pool.checkStraight(txData.FromAddr, txData.ChainID)
}

// checkStraight 检查queued中某一个fromAddr的交易是否能凑成起始牌为当前账户对应nonce的顺子，能凑成的话将顺子从queued移到pending
//
//	@receiver pool
//	@param addrStr
//	@param chainID
func (pool *BaseTxPool) checkStraight(addrStr string, chainID int) {
	pool.queuedMutex.RLock()
	txDescendArray, exist := pool.queued[addrStr]
	pool.queuedMutex.RUnlock()
	if !exist {
		return
	}

	//TODO 多通道，chainID
	startNonce := GetAcountNonceInPool(chainID, addrStr)
	pool.queuedMutex.Lock()
	straightTxs, hasStraightTx := txDescendArray.PackStraightTxs(startNonce)
	pool.queuedMutex.Unlock()
	if !hasStraightTx {
		return
	}

	//将凑成顺子的Tx打包移到pending(先尝试将交易写到状态树，若成功再进pending)
	for _, txData := range straightTxs {
		//保证顺序写入状态树
		pool.inPending(txData)
	}
}

func (pool *BaseTxPool) AddConfirmSubscribe(subCh chan dto.TxData) {
	pool.pendingMutex.Lock()
	pool.confirmSubChs = append(pool.confirmSubChs, subCh)
	pool.pendingMutex.Unlock()
}

func (pool *BaseTxPool) getAcountNonceInPool(chainID int, addrStr string) int64 {
	stateNonce := state.GetAccountNonce(addrStr, chainID)

	pool.pendingMutex.RLock()
	defer pool.pendingMutex.RUnlock()

	txs, exist := pool.pending[addrStr]
	txsLen := len(txs)
	if txsLen == 0 || !exist {
		return stateNonce
	}

	if txs[txsLen-1].Nonce+1 > stateNonce {
		return txs[txsLen-1].Nonce + 1
	}

	return stateNonce
}

func (pool *BaseTxPool) getTxStatus(txData *dto.TxData) state.TxStatus {
	//查看Tx是否已在交易池中
	if pool.IsTxInPool(txData.FromAddr, txData.TxHash) {
		logger.Warn(map[string]interface{}{"[txpool] [Allow Tx In Pool] txpool.IsTxInPool()": "the tx is already in the pool"})
		return state.TxInvalid
	}

	//注册交易和代币解押交易直接进pending
	if txData.TxType == dto.TX_REGISTER || txData.TxType == dto.TX_RELEASE {
		return state.TxPending
	}

	nonce := pool.getAcountNonceInPool(txData.ChainID, txData.FromAddr)
	if nonce == state.InvalidNonce {
		logger.Warn(map[string]interface{}{"[txpool] [Allow Tx In Pool] state.IsTxNonceValid()": "invalid nonce"})
		return state.TxInvalid
	}

	if txData.Nonce == nonce {
		return state.TxPending
	}

	if txData.Nonce > nonce {
		return state.TxQueued
	}

	logger.Warn(map[string]interface{}{"[txpool] [Allow Tx In Pool] state.IsTxNonceValid()": "nonce of tx is lower than latest nonce in pool"})
	return state.TxInvalid
}

// AddTx 将交易加入交易池
//
//	@receiver pool 交易池
//	@param txData 交易
//	@return bool 交易是否被交易池接收
func (pool *BaseTxPool) AddTx(txData *dto.TxData) bool {
	txStatus := pool.getTxStatus(txData)
	logger.Info(map[string]interface{}{fmt.Sprintf("[txpool] [BaseTxPool.AddTx] Tx [type: %d, hash: %s]", txData.TxType, txData.TxHash): fmt.Sprintf("tx status: %d", txStatus)})
	if txStatus == state.TxInvalid {
		logger.Warn(map[string]interface{}{"[txpool] [Add Tx]": "Tx Invalid"})
		return false
	}

	if txStatus == state.TxQueued {
		go func() {
			pool.inQueued(txData)
		}()

		return true
	}

	if txStatus == state.TxPending {
		go func() {
			pool.inPending(txData)
		}()

		return true
	}

	return false
}

func (pool *BaseTxPool) IsTxInPool(fromAddrStr string, txHash string) bool {
	if txDescendArray, exist := pool.queued[fromAddrStr]; exist {
		for _, tx := range txDescendArray.txs {
			if tx.TxHash == txHash {
				return true
			}
		}
	}

	if txs, exist := pool.pending[fromAddrStr]; exist {
		for _, tx := range txs {
			if tx.TxHash == txHash {
				return true
			}
		}
	}

	return false
}

func (pool *BaseTxPool) Pack() []dto.TxData {
	packTxs := make([]dto.TxData, 0)
	pool.pendingMutex.Lock()
	defer pool.pendingMutex.Unlock()

	if len(pool.pending) <= config.MaxTxPackNum {
		for _, txs := range pool.pending {
			for _, tx := range txs {
				packTxs = append(packTxs, *tx)
			}
		}

		return packTxs
	}

	count := 0
	for _, txs := range pool.pending {
		if count > config.MaxTxPackNum {
			break
		}
		for _, tx := range txs {
			packTxs = append(packTxs, *tx)
		}
	}

	return packTxs
}

func (pool *BaseTxPool) OnTxsConfirmed(txs []dto.TxData) {
	pool.pendingMutex.Lock()
	defer pool.pendingMutex.Unlock()

	for _, txData := range txs {
		fromAddr := txData.FromAddr
		tempTxs, exist := pool.pending[fromAddr]
		if !exist {
			continue
		}

		for index, tx := range tempTxs {
			if tx.TxHash != txData.TxHash {
				continue
			}

			//在pending中删除已确认的交易
			deletedTxs := append(tempTxs[:index], tempTxs[index+1:]...)
			pool.pending[fromAddr] = deletedTxs

			//通知交易确认订阅通道
			for _, ch := range pool.confirmSubChs {
				ch <- txData
			}
		}

	}
}

func (pool *BaseTxPool) Truncate() {
	pool.queuedMutex.Lock()
	pool.queued = make(map[string]*TxDescendArray, 0)
	pool.queuedMutex.Unlock()

	pool.pendingMutex.Lock()
	pool.pending = make(map[string][]*dto.TxData, 0)
	pool.pendingMutex.Unlock()
}

func (pool *BaseTxPool) gc() {
	//TODO
}
